﻿using System;
using System.Linq;
using IPCChannel;
using UProveCrypto;

namespace UProveAPI
{
    public delegate void TokensReceivedEventHandler(UProveKeyAndToken[] keysTokens);

    public class Prover
    {
        private IssuerParameters issuerParams;
        private UProveCrypto.Prover prover;
        private IIPCClient ipcClient;
        private byte[] proverInformation;
        private byte[][] attributes;
        private int tokenCount;

        public event TokensReceivedEventHandler TokensReceived;

        public Prover(IIPCClient ipcClient)
        {
            this.ipcClient = ipcClient;
            proverInformation = ipcClient.Encoding().GetBytes("prover information value");
            ipcClient.MessageReceived += IpcClientOnMessageReceived;
            ipcClient.StartListening();
        }

        private void IpcClientOnMessageReceived(IPCMessage message)
        {
            switch (message.MessageType)
            {
                case IPCMessageTypes.SetupMessage:
                    SetupMessage setupMessage = message as SetupMessage;
                    HandleSetupMessage(setupMessage);
                    break;
                case IPCMessageTypes.IssuanceMessage:
                    IssuanceMessage issuanceMsg = message as IssuanceMessage;
                    HandleIssuanceMessage(issuanceMsg);
                    break;
                case IPCMessageTypes.PresentationMessage:
                    PresentationMessage presentationMessage = message as PresentationMessage;
                    HandlePresentationMessage(presentationMessage);
                    break;
                default:
                    break;
            }
        }

        private void HandleSetupMessage(SetupMessage setupMsg)
        {
            switch (setupMsg.DataType)
            {
                case SetupDataType.SetupFinished:
                    Console.WriteLine("Received Issuer Parameters");

                    issuerParams = new IssuerParameters(setupMsg.IssuerParamsJSON);
                    issuerParams.Verify();
                    Console.WriteLine("Issuer is verified!");

                    IssuanceMessage issuanceMsg = new IssuanceMessage(IssuanceDataType.InitalizeIssuance);
                    issuanceMsg.Attributes = attributes;
                    issuanceMsg.TokenCount = tokenCount;
                    issuanceMsg.ProverInformation = proverInformation;

                    ipcClient.Send(setupMsg.SenderIp, setupMsg.SenderPort, issuanceMsg);

                    Console.WriteLine("Sent Issuance initialization message to Issuer");
                    break;
                default:
                    break;
            }
        }

        private void HandleIssuanceMessage(IssuanceMessage issuanceMsg)
        {
            switch (issuanceMsg.DataType)
            {
                case IssuanceDataType.FirstIssuance:
                    Console.WriteLine("Received first Issuance message from Issuer");

                    IssuanceMessage secondMsg = new IssuanceMessage(IssuanceDataType.SecondIssuance);
                    secondMsg.JSONData = IssuanceSecondMessage(issuanceMsg.JSONData, issuanceMsg.TokenInformation);
                    ipcClient.Send(issuanceMsg.SenderIp, issuanceMsg.SenderPort, secondMsg);

                    Console.WriteLine("Sent second Issuance message to Issuer");
                    break;
                case IssuanceDataType.FinalIssuance:
                    Console.WriteLine("Received third Issuance message from Issuer");
                    OnTokensReceived(prover.GenerateTokens(issuerParams.Deserialize<ThirdIssuanceMessage>(issuanceMsg.JSONData)));
                    break;
                default:
                    break;
            }
        }

        private void HandlePresentationMessage(PresentationMessage presentationMsg)
        {
            switch (presentationMsg.DataType)
            {
                case PresentationDataType.TokenVerification:
                    if (presentationMsg.Verified)
                        Console.WriteLine("Hurray! I'm verified!\n");
                    else
                        Console.WriteLine("I'm not verified.. :(\n");
                    break;
                default:
                    break;
            }
        }

        public void RequestTokens(string issuerIp, int issuerPort, byte[][] attributes, int numberOfTokens)
        {
            Console.WriteLine("Requesting tokens from issuer!");

            this.attributes = attributes;
            tokenCount = numberOfTokens;

            SetupMessage setupMessage = new SetupMessage(SetupDataType.InitializeSetup);
            setupMessage.AttributeCount = attributes.Count();
            ipcClient.Send(issuerIp, issuerPort, setupMessage);
        }

        public void PresentToken(string verifierIp, int verifierPort, UProveKeyAndToken token, int[] disclosedIndices)
        {
            Console.WriteLine("Presenting token to Verifier!");

            // The indices of the committed attributes (used by protocol extensions)
            int[] committed = null;

            // The application-specific message that the prover will sign. Typically this is a nonce combined
            // with any application-specific transaction data to be signed.
            byte[] message = ipcClient.Encoding().GetBytes("message");

            // The application-specific verifier scope from which a scope-exclusive pseudonym will be created
            // (if null, then a pseudonym will not be presented)
            byte[] scope = ipcClient.Encoding().GetBytes("verifier scope");

            // The returned commitment randomizer (to be used by an external proof module)
            CommitmentPrivateValues cpv;

            // Generate the presentation proof
            string tokenString = issuerParams.Serialize<UProveToken>(token.Token);
            ProverPresentationProtocolParameters pppp = new ProverPresentationProtocolParameters(issuerParams, disclosedIndices, message, token, attributes);
            pppp.Committed = committed;

            // If a scope is defined, we use the first attribute to derive a scope exclusive pseudonym            
            pppp.PseudonymAttributeIndex = (scope == null ? 0 : 1);
            pppp.PseudonymScope = scope;
            pppp.KeyAndToken = token;
            pppp.Attributes = attributes;
            string proof = issuerParams.Serialize<PresentationProof>(PresentationProof.Generate(pppp, out cpv));

            PresentationMessage presentationMsg = new PresentationMessage(PresentationDataType.PresentToken);
            presentationMsg.SerializedToken = tokenString;
            presentationMsg.PresentationProof = proof;
            presentationMsg.DisclosedIndices = disclosedIndices;
            presentationMsg.Committed = committed;
            presentationMsg.Message = message;
            presentationMsg.Scope = scope;

            ipcClient.Send(verifierIp, verifierPort, presentationMsg);

            Console.WriteLine("Sent Presentation message to Verifier!");
        }

        private string IssuanceSecondMessage(string issuanceMessage, byte[] tokenInfo)
        {
            // Setup the prover and generate the second issuance message
            ProverProtocolParameters ppp = new ProverProtocolParameters(issuerParams);
            ppp.Attributes = attributes;
            ppp.NumberOfTokens = tokenCount;
            ppp.TokenInformation = tokenInfo;
            ppp.ProverInformation = proverInformation;
            prover = ppp.CreateProver();

            return issuerParams.Serialize<SecondIssuanceMessage>(prover.GenerateSecondMessage(issuerParams.Deserialize<FirstIssuanceMessage>(issuanceMessage)));
        }

        protected virtual void OnTokensReceived(UProveKeyAndToken[] keysTokens)
        {
            Console.WriteLine("Created tokens!\n");

            if (TokensReceived != null)
                TokensReceived(keysTokens);
        }
    }
}
